正如之前章节中提到过的,core.convert是一个以通用为目的类型转换系统。它提供了同一个的ConversionService API和强类型的类型转换器SPI来实现一个类型到另一个类型的转换逻辑。一个Spring容器用这个系统来绑定Bean的属性值。另外,Spring Expression Language(SpEL)和DataBinder都使用这个系统去绑定字段值。比如,当SpEL需要强制将一个Short变成Long来完成expression.setValue(Object bean, Object value)尝试时,core.convert系统会执行这个强制转换。
现在,考虑下一些典型的客户端环境下的类型转换需求,比如web或桌面应用。在这样的环境中,你通常需要转成字符串支持客户端回发的过程,以及解析成字符串支持页面渲染的过程。另外,你也需要本地化字符串。一般的core.conveterSPI达不到这种格式化要求。为了现实它们,Spring 3引入了一个便捷的格式化SPI,为客户端运行环境提供了简单健壮的选项代替PropertyEditors。
通常,在你需要实现通用的类型转换逻辑时使用Converter SPI;比如,java.util.Datejava.lang.Long之间的转换。而在你使用客户端环境,如一个web应用,且需要解析和打印本地化的字段值时使用Formatter SPI。ConversionService为两个SPI提供了统一的转换API类型。

8.6.1 Formatter SPI

Formatter SPI实现的字段给实话逻辑是简单且强类型的:

package org.springframework.format;

public interface Formatter<T> extends Printer<T>, Parser<T> {
}

Formatter继承了Printer和Parser接口:

public interface Printer<T> {
    String print(T fieldValue, Locale locale);
}
import java.text.ParseException;

public interface Parser<T> {
    T parse(String clientValue, Locale locale) throws ParseException;
}

为了创建你自己的Formatter,只需要简单的实现上述接口。泛型参数T是你希望格式化的对象的类型,比如,java.util.Date。实现print()方法将T的实例以客户端本地的方式打印出来。实现parse()方法,从客户端语言环境中返回一个格式化标准去解析T的实例。你的Formatter应该在尝试解析失败的时候抛出ParseException或IllegalArgumentException。确保你的Formatter的实现是线程安全的。
为了方便,在format的子包中提供了许多Formatter的实现。number包中提供了NumberFormatterCurrencyFormatterPercentFormatter使用java.text.NumberFormat去格式化java.lang.Numberdatetime包提供了DateFormatter使用java.text.DateFormatter去格式化java.util.Datedatetime.joda包提供了基于Joda Time library复杂的日期格式化支持。
可以将DateFomatter作为Formatter实现的例子:

package org.springframework.format.datetime;

public final class DateFormatter implements Formatter<Date> {

    private String pattern;

    public DateFormatter(String pattern) {
        this.pattern = pattern;
    }

    public String print(Date date, Locale locale) {
        if (date == null) {
            return "";
        }
        return getDateFormat(locale).format(date);
    }

    public Date parse(String formatted, Locale locale) throws ParseException {
        if (formatted.length() == 0) {
            return null;
        }
        return getDateFormat(locale).parse(formatted);
    }

    protected DateFormat getDateFormat(Locale locale) {
        DateFormat dateFormat = new SimpleDateFormat(this.pattern, locale);
        dateFormat.setLenient(false);
        return dateFormat;
    }

}

Spring团队欢迎社区驱动的Formatter贡献;请看jira.spring.io来提供。

8.6.2 Annotation-driven Formatting

如你所见,字段格式化可以被字段类型或是注解配置。实现AnnotationFormatterFactory将注解绑定到Formatter。

package org.springframework.format;

public interface AnnotationFormatterFactory<A extends Annotation> {

    Set<Class<?>> getFieldTypes();

    Printer<?> getPrinter(A annotation, Class<?> fieldType);

    Parser<?> getParser(A annotation, Class<?> fieldType);

}

泛型参数A是你希望关联格式化逻辑的字段注解类型,比如org.springframework.format.annotation.DateTimeFormatgetFieldTypes()会返回可以被使用的字段类型。getPrinter()会返回一个Printer打印被注解的字段的值。getParser()会返回Parser解析一个被注解的客户端值。
下面的AnnotationFormatterFactory的实现将@NumberFormat注解绑定到了formatter上。这个注解允许指定数字风格或格式:

public final class NumberFormatAnnotationFormatterFactory
        implements AnnotationFormatterFactory<NumberFormat> {

    public Set<Class<?>> getFieldTypes() {
        return new HashSet<Class<?>>(asList(new Class<?>[] {
            Short.class, Integer.class, Long.class, Float.class,
            Double.class, BigDecimal.class, BigInteger.class }));
    }

    public Printer<Number> getPrinter(NumberFormat annotation, Class<?> fieldType) {
        return configureFormatterFrom(annotation, fieldType);
    }

    public Parser<Number> getParser(NumberFormat annotation, Class<?> fieldType) {
        return configureFormatterFrom(annotation, fieldType);
    }

    private Formatter<Number> configureFormatterFrom(NumberFormat annotation,
            Class<?> fieldType) {
        if (!annotation.pattern().isEmpty()) {
            return new NumberFormatter(annotation.pattern());
        } else {
            Style style = annotation.style();
            if (style == Style.PERCENT) {
                return new PercentFormatter();
            } else if (style == Style.CURRENCY) {
                return new CurrencyFormatter();
            } else {
                return new NumberFormatter();
            }
        }
    }
}

用@NumberFormat简单注解字段就可以触发格式化:

public class MyModel {

    @NumberFormat(style=Style.CURRENCY)
    private BigDecimal decimal;

}

Format Annotation API

org.springframework.format.annotation包中有简便的格式化注解API。@NumberFormat用来格式化java.lang.Number字段。@DateTimeFormat用来格式化java.util.Datejava.util.Calendarjava.util.Long和Joda Time字段。 下面的例子用@DateTimeFormat将java.util.Date格式化成ISO日期(yyyy-MM-dd):

public class MyModel {

    @DateTimeFormat(iso=ISO.DATE)
    private Date date;

}

8.6.3 FormatterRegistry SPI

FormatterRegistry是一个用来注册formatters和converters的SPI。FormattingConversionService是适用于大多数环境的FormatterRegistry实现。它可以在代码中被配置或是直接用FormattingConversionServiceFactoryBean作为Spring bean使用。由于这个实现也实现了ConversionService。它也可以直接配置被Spring的DataBinder和Spring Expression Language(SpEL)使用。
查看下面的FormatterRegistry SPI:

package org.springframework.format;

public interface FormatterRegistry extends ConverterRegistry {

    void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser);

    void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter);

    void addFormatterForFieldType(Formatter<?> formatter);

    void addFormatterForAnnotation(AnnotationFormatterFactory<?, ?> factory);

}

正如上面所示,Formatter可以通过字段类型或是注解来注册。
FormatterRegistry SPI允许你集中的配置格式化规则,而不是在你的控制器里到处复制这样的配置。比如,你可能希望强制将所有的日期字段以某种方式格式化,或是将有注解的字段以某种方式格式化。通过共享FormatterRegistry,你可以只定义一次这些规则,然后它们会在任何需要格式的地方被应用。

8.6.4 FormatterRegistrat SPI

FormatterRegistrar是一个通过FormatterRegistry注册formatters和conveters的SPI。

package org.springframework.format;

public interface FormatterRegistrar {

    void registerFormatters(FormatterRegistry registry);

}

当你需要为一个给定格式列别(比如日期格式)注册多个关联的conveters和formatter时,FormatterRegistrar是很有用的。在声明性注册不足的情况下也是有用的。例如,formatter需要在与自己的不同的特定字段类型下进行索引,或者在注册Printer/Parser对时进行索引。 下一节提供有关converter和formatter注册的更多信息。

8.6.5 Configuring Formatting in Spring MVC

在Spring MVC那章中查看21.16.3,"Conversion and Formatting"